/*
 * Utilities.cpp
 *
 *  Created on: 05-05-2013
 *      Author: Skeith
 */

#include "Utilities.h"

void extractFace(cv::Mat& image, cv::Mat& output) {
	cv::CascadeClassifier face("haarcascades\\haarcascade_frontalface_default.xml");
	std::vector<cv::Rect> faceROI;
	face.detectMultiScale(image, faceROI, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE | CV_HAAR_FIND_BIGGEST_OBJECT);
	image(faceROI.at(0)).copyTo(output);
}

void drawRects(cv::Mat img, std::vector<cv::Rect> rects, cv::Scalar color) {
	for (unsigned int i = 0; i < rects.size(); i++) {
		cv::rectangle(img, rects.at(i), color, 1, 1);
	}
}

void createSingleHistogramImage(cv::Mat& histogram, cv::Mat& output, int histSize, int histW, int histH) {
	int bin_w = cvRound((double) histW / histSize);

	cv::Mat histImage(histH, histW, CV_8UC3, cv::Scalar(0, 0, 0));

	/// Normalize the result to [ 0, histImage.rows ]
	cv::normalize(histogram, histogram, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());

	/// Draw for each channel
	for (int i = 1; i < histSize; i++) {
		cv::line(histImage, cv::Point(bin_w * (i - 1), histH - cvRound(histogram.at<float>(i - 1))),
				cv::Point(bin_w * (i), histH - cvRound(histogram.at<float>(i))), cv::Scalar(255, 0, 0), 2, 8, 0);
	}

	histImage.copyTo(output);
}

void createProjectionHistogram(cv::Mat& projection, cv::Mat& output, int outSize, int type) {

	if (type == 0) {
		cv::Mat histImage(outSize, projection.cols, CV_8UC1, cv::Scalar(0, 0, 0));
		cv::normalize(projection, projection, 0, outSize, cv::NORM_MINMAX, -1, cv::Mat());

		for (int i = 1; i < projection.cols; i++) {
			float val = projection.at<float>(0, i);
			cv::circle(histImage, cv::Point(i, val), 1, cv::Scalar(255, 255, 255), 1, 1, 0);
		}
		cv::flip(histImage, histImage, 0);
		histImage.copyTo(output);
	}

	if (type == 1) {
		cv::Mat histImage(projection.rows, outSize, CV_8UC1, cv::Scalar(0, 0, 0));
		cv::normalize(projection, projection, 0, outSize, cv::NORM_MINMAX, -1, cv::Mat());

		for (int i = 1; i < projection.rows; i++) {
			float val = projection.at<float>(i, 0);

			cv::circle(histImage, cv::Point(val, i), 1, cv::Scalar(255, 255, 255), 1, 1, 0);
		}
		histImage.copyTo(output);
	}
}

void createHistogram(cv::Mat input, cv::Mat& output) {
	int histSize = 256;
	float range[] = { 0, 256 };
	const float* histRange = { range };

	bool uniform = true;
	bool accumulate = false;

	cv::Mat b_hist;

	/// Compute the histograms:
	cv::calcHist(&input, 1, 0, cv::Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
	cv::Mat histogram;
	createSingleHistogramImage(b_hist, histogram, histSize);

	histogram.copyTo(output);
}

void createProjections(const cv::Mat& input, cv::Mat& hProjection, cv::Mat& vProjection) {
	cv::reduce(input, hProjection, 0, CV_REDUCE_SUM, CV_32FC1);
	cv::reduce(input, vProjection, 1, CV_REDUCE_SUM, CV_32FC1);

}

int faceSize(const cv::Mat& faceImage, int& leftLine, int& rightLine) {
	double min, max;
	cv::Mat hProjection;
	cv::Point minLoc, maxLoc;
	cv::Point minLoc2, maxLoc2;

	cv::Mat cpy;
	faceImage.copyTo(cpy);

	cv::equalizeHist(cpy, cpy);

	cv::reduce(cpy, hProjection, 0, CV_REDUCE_SUM, CV_32FC1);

	cv::Mat m1 = hProjection.colRange(0, hProjection.cols / 3);
	cv::Mat m2 = hProjection.colRange(hProjection.cols - hProjection.cols / 3, hProjection.cols);

	cv::minMaxLoc(m1, &min, &max, &minLoc, &maxLoc);
	cv::minMaxLoc(m2, &min, &max, &minLoc2, &maxLoc2);

	rightLine = minLoc2.x + hProjection.cols - hProjection.cols / 3;
	leftLine = minLoc.x;

	return rightLine - leftLine;
}

cv::Rect findNose(const cv::Mat& input) {

	cv::CascadeClassifier nose("haarcascades\\haarcascade_mcs_nose.xml");
	std::vector<cv::Rect> noseROI;

	int third = input.rows / 3;

	cv::Rect middle(0, third + third / 3, input.cols, third);
	cv::Mat middleFace;
	input(middle).copyTo(middleFace);
	nose.detectMultiScale(middleFace, noseROI, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE | CV_HAAR_FIND_BIGGEST_OBJECT);

	if (noseROI.size() == 1) {
		cv::Rect r = noseROI.at(0);
		r.x += middle.x - 5;
		r.y += middle.y - 5;
		r.width += 10;
		r.height += 10;
		return r;
	}

	return cv::Rect();
}

int findEyesLine(const cv::Mat& input) {

	cv::Mat img;
	cv::Mat vProjection;

	cv::Sobel(input, img, input.depth(), 1, 0, 3);
	cv::reduce(img, vProjection, 1, CV_REDUCE_SUM, CV_32FC1);

	double min, max;
	cv::Point minLoc, maxLoc;
	cv::minMaxLoc(vProjection, &min, &max, &minLoc, &maxLoc);
	return maxLoc.y;
}

void findEyes(const cv::Mat& input, int eyeLine, cv::Mat& outRightEye, cv::Mat& outLeftEye, cv::Rect& rectRightEye, cv::Rect& rectLeftEye) {
	cv::CascadeClassifier eyes("haarcascades\\haarcascade_eye.xml");
	std::vector<cv::Rect> eyesROI;
	cv::Mat eyesRegion;
	cv::Rect upper;

	if (eyeLine == 0) {
		int third = input.rows / 3;
		upper = cv::Rect(0, third - third / 3, input.cols, third);
		input(upper).copyTo(eyesRegion);
	} else {
		int val = 0.12f * input.rows;
		upper = cv::Rect(0, eyeLine - val, input.cols, (2 * val));
		input(upper).copyTo(eyesRegion);
	}

	cv::imshow("eyes", eyesRegion);
	eyes.detectMultiScale(eyesRegion, eyesROI, 1.2, 4, 0 | CV_HAAR_SCALE_IMAGE | CV_HAAR_FEATURE_MAX);

	if (eyesROI.size() == 2) {
		cv::Rect r1 = eyesROI.at(0);
		cv::Rect r2 = eyesROI.at(1);

		r1.x += upper.x; // - 5;
		r1.y += upper.y; // - 5;
		r1.width += 10;
		//r1.height += 10;

		r2.x += upper.x; // - 5;
		r2.y += upper.y; // - 5;
		r2.width += 10;
		//r2.height += 10;

		if (r1.x < r2.x) {
			input(r1).copyTo(outLeftEye);
			input(r2).copyTo(outRightEye);

			rectLeftEye = r1;
			rectRightEye = r2;
		} else {
			input(r2).copyTo(outLeftEye);
			input(r1).copyTo(outRightEye);

			rectLeftEye = cv::Rect(r2);
			rectRightEye = cv::Rect(r1);
		}
	}
}

void findEyeCenter(const cv::Mat& input, cv::Point& center, int& radius) {

	cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(2 * 3 + 1, 2 * 3 + 1), cv::Point(3, 3));
	//cv::erode(rEye, rEye, element);

	cv::Mat cpy;
	cv::Mat edges;
	cv::Mat eyeTmp;

	input.copyTo(cpy);

	//cv::blur(cpy, cpy, cv::Size(3, 3));
	//smooth it, otherwise a lot of false circles may be detected
	cv::GaussianBlur(cpy, cpy, cv::Size(3, 3), 2, 2);
	cv::erode(cpy, cpy, element);

	cv::Canny(cpy, edges, 60, 130, 3);
	input.copyTo(eyeTmp, edges);
	//cv::adaptiveThreshold(rEye, rEye, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 3, 5);

	std::vector<cv::Vec3f> circles;
	cv::HoughCircles(eyeTmp, circles, CV_HOUGH_GRADIENT, 1, eyeTmp.rows / 4, 150, 15, 0, 0);

	if (!circles.empty()) {
		center = cv::Point(cvRound(circles[0][0]), cvRound(circles[0][1]));
		radius = cvRound(circles[0][2]);
	}

	// circle center
	//cv::circle(cpy, center, 3, cv::Scalar(255, 0, 0), 1, 2, 0);
	// circle outline
	//cv::circle(cpy, center, radius, cv::Scalar(255, 0, 0), 1, 2, 0);

	//cv::imshow("Eye", input);
	cv::imshow("EyeTmp", eyeTmp);
	//cv::imshow("Cpy", cpy);
}

cv::Rect findMouth(const cv::Mat& input) {

	cv::CascadeClassifier mouth("haarcascades\\haarcascade_mcs_mouth.xml");
	std::vector<cv::Rect> mouthROI;

	int third = input.rows / 3;

	cv::Rect lower(0, third * 2, input.cols, third);
	cv::Mat lowerFace;

	input(lower).copyTo(lowerFace);
	mouth.detectMultiScale(lowerFace, mouthROI, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE | CV_HAAR_FIND_BIGGEST_OBJECT);

	if (mouthROI.size() == 1) {
		cv::Rect r = mouthROI.at(0);
		r.x += lower.x - 10;
		r.y += lower.y - 10;
		r.width += 20;
		r.height += 20;
		return r;
	}

	return cv::Rect();
}
